//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.filesharing;

import java.util.*;
import java.beans.*;
import java.io.*;

import ants.p2p.*;
import ants.p2p.messages.*;
import ants.p2p.security.EndpointSecurityManager;
import ants.p2p.utils.indexer.*;
import ants.p2p.utils.encoding.*;
import ants.p2p.query.*;

import org.apache.log4j.*;

import kerjodando.casper.util.StringUtil;
import kerjodando.casper.util.ConstantUtil;

public class MultipleSourcesDownloadManager extends Thread {

	static Logger _logger = Logger
			.getLogger(MultipleSourcesDownloadManager.class.getName());

	MultipleSourcesDownloadManager instance = null;

	AutoresumeEngine ae;

	String fileName;

	String fileHash;

	String ed2kFileHash;

	Object[] chunkHashes;

	String extendedInfos;

	long bytesDownloaded;

	long initialBytesDownloaded;

	long fileSize;

	int blockSize;

	java.util.List peerList = new ArrayList();

	java.util.List synchronizedPeerList = peerList;// Collections.synchronizedList(peerList);

	java.util.List inServicePeerList = new ArrayList();

	java.util.List synchronizedInServicePeerList = inServicePeerList;// Collections.synchronizedList(inServicePeerList);

	WarriorAnt n;

	boolean interrupt = false;

	boolean writingToDisk = false;

	long percWritten = 0;

	boolean[] downloadedBlockGroups;

	boolean[] inServiceBlockGroups;

	int partId;

	DownloadWatchDog watchDog = new DownloadWatchDog(this);

	long assignedCoprime;

	PropertyChangeListener selfListener = null;

	PropertyChangeListener warriorAntListener = null;

	long startTime;

	// long lastPartCompletedTime;

	public static int blocksPerSource;

	public static int MaxSources = 5;

	public PropertyChangeSupport propertyChangeSupport;

	int chunckBlocksCounter = 0;

	public MultipleSourcesDownloadManager(WarriorAnt n, String fileName,
			String fileHash, String ed2kFileHash, long bytesDownloaded,
			int blockSize, long fileSize, String extendedInfos)
			throws Exception {
		instance = this;
		this.fileName = fileName;
		this.fileHash = fileHash;
		this.ed2kFileHash = ed2kFileHash;
		this.chunkHashes = null;
		this.extendedInfos = extendedInfos;
		this.bytesDownloaded = bytesDownloaded;
		this.initialBytesDownloaded = bytesDownloaded;
		this.fileSize = fileSize;
		this.blockSize = blockSize;
		this.blocksPerSource = PartialFile.computeGroupFactor(blockSize);
		this.n = n;
		this.setPriority(6);
		startTime = System.currentTimeMillis();
		downloadedBlockGroups = new boolean[(int) Math.ceil(fileSize
				/ ((blockSize * blocksPerSource) * 1.0))];
		inServiceBlockGroups = new boolean[downloadedBlockGroups.length];

		partId = 0;

		this.assignedCoprime = (long) Math.floor(Math.random()
				* downloadedBlockGroups.length);
		while (!((new java.math.BigInteger(this.assignedCoprime + ""))
				.gcd(new java.math.BigInteger(downloadedBlockGroups.length + ""))
				.equals(java.math.BigInteger.ONE))) {
			this.assignedCoprime++;
			if (this.assignedCoprime == downloadedBlockGroups.length) {
				this.assignedCoprime = (long) Math.floor(Math.random()
						* downloadedBlockGroups.length);
			}
		}

		Arrays.fill(downloadedBlockGroups, false);
		Arrays.fill(inServiceBlockGroups, false);
		this.propertyChangeSupport = new PropertyChangeSupport(this);

		this.selfListener = new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent e) {
				if (e.getPropertyName().equals("peerAvaiable")) {
					resume();
				}
			}
		};
		this.propertyChangeSupport.addPropertyChangeListener(this.selfListener);

		this.warriorAntListener = new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent e) {
				if (e.getPropertyName().equals("filePartUpdate")) {
					if (((FilePartMessage) e.getNewValue())
							.getFilePushMessage().getFileName().lastIndexOf(
									getFileHash()) >= 0) {
						chunckBlocksCounter++;
						watchDog.setReceivedPart();
						String peerId = ((FilePartMessage) e.getNewValue())
								.getFilePushMessage().getSource();
						propertyChangeSupport.firePropertyChange(
								"pullerThreadUpdatePart", null, peerId);
					}
				}
				if (e.getPropertyName().equals("filePartDownloadCompleted")) {
					if (((FilePushMessage) e.getNewValue()).getFileName()
							.lastIndexOf(getFileHash()) >= 0) {
						chunckBlocksCounter -= blocksPerSource;
						if (chunckBlocksCounter < 0)
							chunckBlocksCounter = 0;
						String peerId = ((FilePushMessage) e.getNewValue())
								.getSource();
						String suffix = ((FilePushMessage) e.getNewValue())
								.getFileName();
						int index = suffix.lastIndexOf(".");
						suffix = suffix.substring(index + 1, suffix.length());
						int id = Integer.parseInt(suffix);
						watchDog.setReceivedPart();
						propertyChangeSupport.firePropertyChange(
								"pullerThreadCompletedPart", null, peerId);
						synchronized (downloadedBlockGroups) {
							synchronized (inServiceBlockGroups) {
								synchronized (synchronizedInServicePeerList) {
									synchronized (synchronizedPeerList) {
										try {
											DigestManager dm = new DigestManager();
											if (dm
													.getDigest(
															new File(
																	WarriorAnt.chunksHome
																			+ ((FilePushMessage) e
																					.getNewValue())
																					.getFileName()))
													.equals(
															Base16
																	.toHexString((byte[]) chunkHashes[id]))) {
												_logger
														.info("File part accepted: "
																+ Base16
																		.toHexString((byte[]) chunkHashes[id]));
												downloadedBlockGroups[id] = true;
												NeighbourAnt.totalDownloaded += (new File(
														WarriorAnt.chunksHome
																+ ((FilePushMessage) e
																		.getNewValue())
																		.getFileName()))
														.length();
											} else {
												_logger
														.info("File part rejected: "
																+ Base16
																		.toHexString((byte[]) chunkHashes[id]));
											}
										} catch (Exception exc) {
											_logger
													.error(
															"Error in computing digest "
																	+ e != null ? e
																	.getNewValue()
																	: e
																			+ " "
																			+ chunkHashes != null ? chunkHashes[id]
																			: chunkHashes,
															exc);
										}
										inServiceBlockGroups[id] = false;
										QueryFileTuple peer = findInServicePeer(peerId);
										if (peer != null) {
											synchronizedInServicePeerList
													.remove(peer);
											synchronizedPeerList.add(peer);
										}
										resume();
									}
								}
							}
						}
					}
				}
				if (e.getPropertyName().equals("filePartDownloadError")) {
					if (((FileTransferErrorControlMessage) e.getNewValue())
							.getFilePullMessage().getLocalFileName()
							.lastIndexOf(getFileHash()) >= 0) {
						chunckBlocksCounter -= blocksPerSource;
						if (chunckBlocksCounter < 0)
							chunckBlocksCounter = 0;
						final String peerId = ((FileTransferErrorControlMessage) e
								.getNewValue()).getSource();
						propertyChangeSupport.firePropertyChange(
								"pullerThreadPartError", null, peerId);
						String suffix = ((FileTransferErrorControlMessage) e
								.getNewValue()).getFilePullMessage()
								.getLocalFileName();
						int index = suffix.lastIndexOf(".");
						suffix = suffix.substring(index + 1, suffix.length());
						int id = Integer.parseInt(suffix);
						synchronized (inServiceBlockGroups) {
							synchronized (synchronizedInServicePeerList) {
								inServiceBlockGroups[id] = false;
								if (((FileTransferErrorControlMessage) e
										.getNewValue()).getControlId()
										.intValue() == 0) {
									removePeer(peerId);
									Thread firer = new Thread() {
										public void run() {
											propertyChangeSupport
													.firePropertyChange(
															"removedSourcePeer",
															instance, peerId);
										}
									};
									firer.start();
								} else if (((FileTransferErrorControlMessage) e
										.getNewValue()).getControlId()
										.intValue() == 1) {
									Thread remover = new Thread() {
										public void run() {
											_logger
													.info("Waiting... 3 minutes for:\n"
															+ instance
															+ "\n"
															+ peerId);
											try {
												sleep(2 * 60 * 1000);
											} catch (InterruptedException ex) {
											}
											synchronized (synchronizedInServicePeerList) {
												synchronized (synchronizedPeerList) {
													QueryFileTuple peer = findInServicePeer(peerId);
													if (peer != null) {
														synchronizedInServicePeerList
																.remove(peer);
														synchronizedPeerList
																.add(peer);
													}
												}
											}
										}
									};
									remover.start();
								}
								resume();
							}
						}
					}
				}
				if (e.getPropertyName().equals("filePartDownloadInterrupted")) {
					if (((FilePushMessage) e.getNewValue()).getFileName()
							.lastIndexOf(getFileHash()) >= 0) {
						chunckBlocksCounter -= blocksPerSource;
						if (chunckBlocksCounter < 0)
							chunckBlocksCounter = 0;
						final String peerId = ((FilePushMessage) e
								.getNewValue()).getSource();
						propertyChangeSupport.firePropertyChange(
								"pullerThreadPartError", null, peerId);
						String suffix = ((FilePushMessage) e.getNewValue())
								.getFileName();
						int index = suffix.lastIndexOf(".");
						suffix = suffix.substring(index + 1, suffix.length());
						int id = Integer.parseInt(suffix);
						synchronized (inServiceBlockGroups) {
							synchronized (synchronizedInServicePeerList) {
								inServiceBlockGroups[id] = false;
								removePeer(peerId);
								Thread firer = new Thread() {
									public void run() {
										propertyChangeSupport
												.firePropertyChange(
														"removedSourcePeer",
														instance, peerId);
									}
								};
								firer.start();
								resume();
							}
						}
					}
				}
				if (e.getPropertyName().equals("fileInfosPullCompleted")) {
					if (Base16.toHexString(
							((FileInfosPushMessage) e.getNewValue()).getHash())
							.equals(getFileHash())) {
						final QueryFileTuple qft = ((FileInfosPushMessage) e
								.getNewValue()).getQueryFileTuple();
						final String peerId = qft.getOwnerID();
						propertyChangeSupport.firePropertyChange(
								"fileInfosPullCompleteThread", null, peerId);
						final QueryFileTuple peer = findInServicePeer(peerId);
						watchDog.setReceivedPart();
						Thread firer = new Thread() {
							public void run() {
								_logger.info("Checking chunks...");
								if (qft.getChunkHashes().getChunkHashes() == null
										&& instance.getChunkHashes() == null) {
									try {
										_logger
												.info("Waiting... 2 minutes for chunks:\n"
														+ instance
														+ "\n"
														+ peerId);
										sleep(2 * 60 * 1000);
									} catch (InterruptedException ex) {
									}
								}
								synchronized (synchronizedInServicePeerList) {
									synchronized (synchronizedPeerList) {
										if (peer != null) {
											synchronizedInServicePeerList
													.remove(peer);
											synchronizedPeerList.add(peer);
										}
									}
								}
								propertyChangeSupport.firePropertyChange(
										"addSourcePeer", instance, qft);
							}
						};
						firer.start();
						resume();
					}
				}
				if (e.getPropertyName().equals("fileInfosPullError")) {
					String hash = null;
					if (e.getNewValue() instanceof FileInfosPullErrorControlMessage) {
						hash = Base16
								.toHexString(((FileInfosPullErrorControlMessage) e
										.getNewValue()).getContent());
					}
					if (e.getNewValue() instanceof FileInfosPushMessage) {
						hash = Base16.toHexString(((FileInfosPushMessage) e
								.getNewValue()).getHash());
					}
					if (hash.equals(getFileHash())) {
						final String peerId = ((Message) e.getNewValue())
								.getSource();
						propertyChangeSupport.firePropertyChange(
								"fileInfosPullErrorThread", null, peerId);
						synchronized (synchronizedInServicePeerList) {
							removePeer(peerId);
							Thread firer = new Thread() {
								public void run() {
									propertyChangeSupport.firePropertyChange(
											"removedSourcePeer", instance,
											peerId);
								}
							};
							firer.start();
							resume();
						}
					}
				}
				if (e.getPropertyName().equals("infosPullerThreadRequestError")) {
					if (((FileInfosPullerThread) e.getNewValue()).fileHash
							.equals(getFileHash())) {
						final String peerId = ((FileInfosPullerThread) e
								.getNewValue()).peerId;
						synchronized (synchronizedInServicePeerList) {
							_logger.info("Infos puller thread request error");
							removePeer(peerId);
							Thread firer = new Thread() {
								public void run() {
									propertyChangeSupport.firePropertyChange(
											"removedSourcePeer", instance,
											peerId);
								}
							};
							firer.start();
							resume();
						}
					}
				}
				if (e.getPropertyName().equals("filePullerThreadRequestError")) {
					if (((PullerThread) e.getNewValue()).fileHash
							.equals(getFileHash())) {
						final String peerId = ((PullerThread) e.getNewValue()).peerId;
						int id = ((PullerThread) e.getNewValue()).partId;
						synchronized (inServiceBlockGroups) {
							synchronized (synchronizedInServicePeerList) {
								inServiceBlockGroups[id] = false;
								removePeer(peerId);
								_logger
										.info("File puller thread request error");
								Thread firer = new Thread() {
									public void run() {
										propertyChangeSupport
												.firePropertyChange(
														"removedSourcePeer",
														instance, peerId);
									}
								};
								firer.start();
								resume();
							}
						}
					}
				}
				if (e.getPropertyName().equals("filePullRequestError")) {
					if (((FilePullMessage) e.getNewValue()).getLocalFileName()
							.lastIndexOf(getFileHash()) >= 0) {
						final String peerId = ((FilePullMessage) e
								.getNewValue()).getDest();
						propertyChangeSupport.firePropertyChange(
								"pullerThreadPartError", null, peerId);
						String suffix = ((FilePullMessage) e.getNewValue())
								.getLocalFileName();
						int index = suffix.lastIndexOf(".");
						suffix = suffix.substring(index + 1, suffix.length());
						int id = Integer.parseInt(suffix);
						synchronized (inServiceBlockGroups) {
							synchronized (synchronizedInServicePeerList) {
								inServiceBlockGroups[id] = false;
								removePeer(peerId);
								_logger.info("File pull request error");
								Thread firer = new Thread() {
									public void run() {
										propertyChangeSupport
												.firePropertyChange(
														"removedSourcePeer",
														instance, peerId);
									}
								};
								firer.start();
								resume();
							}
						}
					}
				}
				if (e.getPropertyName().equals("filePushError")) {
					if (((FilePushMessage) e.getNewValue()).getFileName()
							.lastIndexOf(getFileHash()) >= 0) {
						final String peerId = ((FilePushMessage) e
								.getNewValue()).getSource();
						propertyChangeSupport.firePropertyChange(
								"pullerThreadPartError", null, peerId);
						String suffix = ((FilePushMessage) e.getNewValue())
								.getFileName();
						int index = suffix.lastIndexOf(".");
						suffix = suffix.substring(index + 1, suffix.length());
						int id = Integer.parseInt(suffix);
						synchronized (inServiceBlockGroups) {
							synchronized (synchronizedInServicePeerList) {
								inServiceBlockGroups[id] = false;
								removePeer(peerId);
								Thread firer = new Thread() {
									public void run() {
										propertyChangeSupport
												.firePropertyChange(
														"removedSourcePeer",
														instance, peerId);
									}
								};
								firer.start();
								resume();
							}
						}
					}
				}
				if (e.getPropertyName().equals("filePartError")) {
					if (((FilePartMessage) e.getNewValue())
							.getFilePushMessage().getFileName().lastIndexOf(
									getFileHash()) >= 0) {
						final String peerId = ((FilePartMessage) e
								.getNewValue()).getSource();
						propertyChangeSupport.firePropertyChange(
								"pullerThreadPartError", null, peerId);
						String suffix = ((FilePartMessage) e.getNewValue())
								.getFilePushMessage().getFileName();
						int index = suffix.lastIndexOf(".");
						suffix = suffix.substring(index + 1, suffix.length());
						int id = Integer.parseInt(suffix);
						synchronized (inServiceBlockGroups) {
							synchronized (synchronizedInServicePeerList) {
								inServiceBlockGroups[id] = false;
								removePeer(peerId);
								Thread firer = new Thread() {
									public void run() {
										propertyChangeSupport
												.firePropertyChange(
														"removedSourcePeer",
														instance, peerId);
									}
								};
								firer.start();
								resume();
							}
						}
					}
				}
				if (e.getPropertyName().equals("securityConnectionError")) {
					_logger
							.error("Critical Security Error: stopping all downloads from "
									+ (String) e.getOldValue());
				}
			}
		};
		n.getPropertyChangeSupport().addPropertyChangeListener(
				this.warriorAntListener);

		WarriorAnt.checkChunksPath();
		File store = new File(WarriorAnt.chunksHome + WarriorAnt.chunksPath
				+ fileHash + ".mul");

		if (store.exists()) {
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
					store));
			try {
				this.downloadedBlockGroups = (boolean[]) ois.readObject();
				this.fileName = (String) ois.readObject();
				this.fileHash = (String) ois.readObject();
				this.ed2kFileHash = (String) ois.readObject();
				this.chunkHashes = (Object[]) ois.readObject();
				this.bytesDownloaded = ((Long) ois.readObject()).longValue();
				this.initialBytesDownloaded = bytesDownloaded;
				this.fileSize = ((Long) ois.readObject()).longValue();
				this.blockSize = ((Integer) ois.readObject()).intValue();
				this.assignedCoprime = ((Long) ois.readObject()).longValue();
				String ieDummy = (String) ois.readObject();
				ois.close();
			} catch (Exception e) {
				ois.close();
				store.delete();
			}
		}
		this.checkDownloadedBlockGroups();
		if (!store.exists()) {
			ObjectOutputStream oos = new ObjectOutputStream(
					new FileOutputStream(store));
			oos.writeObject(downloadedBlockGroups);
			oos.writeObject(fileName);
			oos.writeObject(fileHash);
			oos.writeObject(ed2kFileHash);
			oos.writeObject(chunkHashes);
			oos.writeObject(new Long(bytesDownloaded));
			oos.writeObject(new Long(fileSize));
			oos.writeObject(new Integer(blockSize));
			oos.writeObject(new Long(assignedCoprime));
			oos.writeObject(extendedInfos);
			oos.flush();
			oos.close();
		}
		if (this.chunkHashes != null)
			BackgroundEngine.getInstance().addPartialFile(this);
	}

	public int getPeersNumber() {
		return this.peerList.size() + this.inServicePeerList.size();
	}

	public String getPeerId() {
		String s = StringUtil.EMPTY_STRING;
		synchronized (this.synchronizedInServicePeerList) {
			if (synchronizedInServicePeerList.size() != 0) {
				EndpointSecurityManager ep = n
						.getOutputSecureConnectionManager(((QueryFileTuple) synchronizedInServicePeerList.get(0))
								.getOwnerID());
				if (ep != null)
					s = ep.getPeerId();
			}
			return s;
		}
	}

	public int getAvaiablePreviewIndexes() {
		int x = 0;
		while (this.downloadedBlockGroups[x]) {
			x++;
		}
		return x;
	}

	public boolean addPeer(QueryFileTuple peerId) {
		// Solo se c'���una connessione sicura
		synchronized (this.synchronizedInServicePeerList) {
			synchronized (this.synchronizedPeerList) {
				if (peerId instanceof QueryCompletedFileTuple) {
					QueryCompletedFileTuple qftPeerId = (QueryCompletedFileTuple) peerId;
					if (this.chunkHashes == null
							&& peerId.getChunkHashes().getChunkHashes() != null
							&& peerId.checkHashesValidity()) {
						this.chunkHashes = peerId.getChunkHashes()
								.getChunkHashes();
						BackgroundEngine.getInstance().addPartialFile(this);
						this.propertyChangeSupport.firePropertyChange(
								"peerAvaiable", null, null);
					}
					if (n.getOutputSecureConnectionManager(qftPeerId
							.getOwnerID()) != null) {
						if (!this.synchronizedPeerList.contains(qftPeerId)
								&& !this.synchronizedInServicePeerList
										.contains(qftPeerId)
								&& ((this.synchronizedPeerList.size() + this.synchronizedInServicePeerList
										.size()) < MultipleSourcesDownloadManager.MaxSources)) {
							this.synchronizedPeerList.add(qftPeerId);
							this.propertyChangeSupport.firePropertyChange(
									"peerAvaiable", null, null);
							return true;
						} else if (this.synchronizedPeerList
								.contains(qftPeerId)) {
							this.synchronizedPeerList.remove(qftPeerId);
							this.synchronizedPeerList.add(qftPeerId);
							return true;
						} else if (this.synchronizedInServicePeerList
								.contains(qftPeerId)) {
							this.synchronizedInServicePeerList
									.remove(qftPeerId);
							this.synchronizedInServicePeerList.add(qftPeerId);
							return true;
						}
					}
				} else if (peerId instanceof QueryPartialFileTuple) {
					QueryPartialFileTuple qpftPeerId = (QueryPartialFileTuple) peerId;
					if (this.chunkHashes == null
							&& peerId.getChunkHashes() != null
							&& peerId.checkHashesValidity()) {
						this.chunkHashes = peerId.getChunkHashes()
								.getChunkHashes();
						this.propertyChangeSupport.firePropertyChange(
								"peerAvaiable", null, null);
					}
					if (n.getOutputSecureConnectionManager(qpftPeerId
							.getOwnerID()) != null) {
						if (!this.synchronizedPeerList.contains(qpftPeerId)
								&& !this.synchronizedInServicePeerList
										.contains(qpftPeerId)
								&& ((this.synchronizedPeerList.size() + this.synchronizedInServicePeerList
										.size()) < MultipleSourcesDownloadManager.MaxSources)) {
							if (this.getBlockSize() == qpftPeerId
									.getBlockSize().intValue()
									&& blocksPerSource == qpftPeerId
											.getBlocksPerSource().intValue()) {
								this.synchronizedPeerList.add(qpftPeerId);
								this.propertyChangeSupport.firePropertyChange(
										"peerAvaiable", null, null);
								return true;
							}
						} else if (this.synchronizedPeerList
								.contains(qpftPeerId)) {
							if (this.getBlockSize() == qpftPeerId
									.getBlockSize().intValue()
									&& blocksPerSource == qpftPeerId
											.getBlocksPerSource().intValue()) {
								this.synchronizedPeerList.remove(qpftPeerId);
								this.synchronizedPeerList.add(qpftPeerId);
								return true;
							}
						} else if (this.synchronizedInServicePeerList
								.contains(qpftPeerId)) {
							if (this.getBlockSize() == qpftPeerId
									.getBlockSize().intValue()
									&& blocksPerSource == qpftPeerId
											.getBlocksPerSource().intValue()) {
								this.synchronizedInServicePeerList
										.remove(qpftPeerId);
								this.synchronizedInServicePeerList
										.add(qpftPeerId);
								return true;
							}
						}
					}
				} else if (peerId instanceof QueryRemoteFileTuple) {
					QueryRemoteFileTuple qrftPeerId = (QueryRemoteFileTuple) peerId;
					_logger.info(!this.n.getIdent().equals(
							qrftPeerId.getOwnerID())
							+ "");
					_logger.info((qrftPeerId.getOwnerIP().equals("") || !this.n
							.getLocalInetAddress().equals(
									qrftPeerId.getOwnerIP()))
							+ "");
					_logger.info(!this.synchronizedPeerList
							.contains(qrftPeerId)
							+ "");
					_logger.info(!this.synchronizedInServicePeerList
							.contains(qrftPeerId)
							+ "");
					if (!this.n.getIdent().equals(qrftPeerId.getOwnerID())
							&& (qrftPeerId.getOwnerIP().equals("") || !this.n
									.getLocalInetAddress().equals(
											qrftPeerId.getOwnerIP()))
							&& !this.synchronizedPeerList.contains(qrftPeerId)
							&& !this.synchronizedInServicePeerList
									.contains(qrftPeerId)
							&& ((this.synchronizedPeerList.size() + this.synchronizedInServicePeerList
									.size()) < MultipleSourcesDownloadManager.MaxSources)) {
						this.synchronizedPeerList.add(qrftPeerId);
						this.propertyChangeSupport.firePropertyChange(
								"peerAvaiable", null, null);
						return true;
					}
				}
			}
		}
		return false;
	}

	public List getPeerList() {
		return this.synchronizedPeerList;
	}

	public QueryFileTuple removePeer(String peerID) {
		// Solo se c'���una connessione sicura
		synchronized (this.synchronizedInServicePeerList) {
			synchronized (this.synchronizedPeerList) {
				QueryFileTuple peer = this.findPeer(peerID);
				if (peer != null) {
					this.synchronizedPeerList.remove(peer);
					return peer;
				}
				QueryFileTuple ispeer = this.findInServicePeer(peerID);
				if (ispeer != null) {
					this.synchronizedInServicePeerList.remove(ispeer);
					return ispeer;
				}
			}
		}
		return null;
	}

	public void removeAllPeers(boolean deactivateAutoresume) {
		if (isAutoresumeActive() && deactivateAutoresume) {
			deactivateAutoresumeEngine();
		}
		// synchronized (this.synchronizedInServicePeerList) {
		// synchronized (this.synchronizedPeerList) {
		for (int x = this.synchronizedPeerList.size() - 1; x >= 0; x--) {
			propertyChangeSupport.firePropertyChange("removedSourcePeer", this,
					((QueryFileTuple) this.synchronizedPeerList.get(x))
							.getOwnerID());
		}
		for (int x = this.synchronizedInServicePeerList.size() - 1; x >= 0; x--) {
			propertyChangeSupport
					.firePropertyChange(
							"removedSourcePeer",
							this,
							((QueryFileTuple) this.synchronizedInServicePeerList
									.get(x)).getOwnerID());
		}
		// }
		// }
	}

	public void setInterrupt(boolean deactivateAutoresume) {
		if (isAutoresumeActive() && deactivateAutoresume) {
			deactivateAutoresumeEngine();
		}
		// synchronized(this.synchronizedInServicePeerList){
		// synchronized (this.synchronizedPeerList) {
		this.interrupt = true;
		this.resume();
		// }
		// }
	}

	public void resetInterrupt() {
		this.interrupt = false;
		this.resume();
	}

	public void removeAllPropertyChangeListeners() {
		PropertyChangeListener[] listeners = this.propertyChangeSupport
				.getPropertyChangeListeners();
		for (int x = 0; x < listeners.length; x++) {
			this.propertyChangeSupport
					.removePropertyChangeListener(listeners[x]);
		}
		n.getPropertyChangeSupport().removePropertyChangeListener(
				this.warriorAntListener);
	}

	public void run() {
		try {
			startTime = System.currentTimeMillis();
			WarriorAnt.checkChunksPath();
			this.watchDog.start();
			partId = 0;
			while (!n.isDisconnected() && !this.checkCompleted()) {
				if (this.synchronizedPeerList.size() == 0
						|| this.synchronizedInServicePeerList.size() == MaxSources) {
					this.propertyChangeSupport.firePropertyChange(
							"multipleDownloadSuspended", null, this);
					_logger.debug(this + "\nsuspended! InService["
							+ this.synchronizedInServicePeerList.size()
							+ "] Idle[" + this.synchronizedPeerList.size()
							+ "]");
					suspend();
				}
				if (interrupt) {
					_logger.info(this + "\ninterrupting! InService["
							+ this.synchronizedInServicePeerList.size()
							+ "] Idle[" + this.synchronizedPeerList.size()
							+ "]");
					while (this.synchronizedInServicePeerList.size() > 0) {
						Thread.sleep(1000);
					}
					this.removeAllPropertyChangeListeners();
					File store = new File(WarriorAnt.chunksHome
							+ WarriorAnt.chunksPath + fileHash + ".mul");
					ObjectOutputStream oos = new ObjectOutputStream(
							new FileOutputStream(store));
					oos.writeObject(downloadedBlockGroups);
					oos.writeObject(fileName);
					oos.writeObject(fileHash);
					oos.writeObject(ed2kFileHash);
					oos.writeObject(chunkHashes);
					int num = 0;
					for (int x = 0; x < this.downloadedBlockGroups.length; x++) {
						if (this.downloadedBlockGroups[x] == true) {
							num++;
						}
					}
					this.bytesDownloaded = num * (blockSize * blocksPerSource);
					oos.writeObject(new Long(bytesDownloaded));
					oos.writeObject(new Long(fileSize));
					oos.writeObject(new Integer(blockSize));
					oos.writeObject(new Long(assignedCoprime));
					oos.writeObject(extendedInfos);
					oos.close();
					_logger.info(this.getFileHash()
							+ ": MultipleSourcesDownloadManager stopped");
					this.watchDog.terminate();
					return;
				}
				synchronized (downloadedBlockGroups) {
					synchronized (inServiceBlockGroups) {
						synchronized (synchronizedInServicePeerList) {
							synchronized (synchronizedPeerList) {
								for (int t = this.synchronizedPeerList.size() - 1; t >= 0
										&& this.synchronizedInServicePeerList
												.size() < MaxSources
										&& !this.checkCompleted(); t--) {
									final QueryFileTuple peerTuple = (QueryFileTuple) this.synchronizedPeerList
											.get(t);
									if (peerTuple instanceof QueryCompletedFileTuple
											&& this.chunkHashes != null) {
										QueryCompletedFileTuple peerFileTuple = (QueryCompletedFileTuple) peerTuple;
										final String peerId = peerFileTuple
												.getOwnerID();
										boolean requiredPart = false;
										partId = 0;
										do {
											if (downloadedBlockGroups[partId] == false
													&& inServiceBlockGroups[partId] == false) {
												this.synchronizedPeerList
														.remove(t);
												final long curOffset = partId
														* (blockSize * blocksPerSource);
												File resume = new File(
														WarriorAnt.chunksHome
																+ WarriorAnt.chunksPath
																+ fileHash
																+ "." + partId);
												if (resume.exists()) {
													try {
														DigestManager dm = new DigestManager();
														if (dm
																.getDigest(
																		resume)
																.equals(
																		Base16
																				.toHexString((byte[]) chunkHashes[partId]))) {
															_logger
																	.info("File part already downloaded: "
																			+ Base16
																					.toHexString((byte[]) chunkHashes[partId]));
															downloadedBlockGroups[partId] = true;
															inServiceBlockGroups[partId] = false;
															partId = (int) ((partId + this.assignedCoprime) % downloadedBlockGroups.length);
															continue;
														} else {
															resume.delete();
															_logger
																	.info("File part rejected(deleted): "
																			+ Base16
																					.toHexString((byte[]) chunkHashes[partId]));
														}
													} catch (Exception exc) {
														_logger
																.error("Error in computing digest, deleting...");
														resume.delete();
													}
												}
												_logger.info(super.toString()
														+ " Pulling file "
														+ fileName
														+ " from id " + peerId
														+ " - " + fileHash
														+ "." + partId);
												_logger.info(fileName + " "
														+ blocksPerSource + " "
														+ blockSize);
												requiredPart = true;
												this.synchronizedInServicePeerList
														.add(peerTuple);
												_logger
														.info("Idle: "
																+ this.synchronizedPeerList
																		.size()
																+ "    In service: "
																+ this.synchronizedInServicePeerList
																		.size());
												inServiceBlockGroups[partId] = true;
												PullerThread puller = new PullerThread(
														this, n, fileName,
														fileHash, curOffset,
														blocksPerSource,
														peerId, blockSize,
														WarriorAnt.chunksPath
																+ fileHash
																+ "." + partId,
														false, peerTuple,
														partId);
												puller.start();
											}
											partId = (int) ((partId + this.assignedCoprime) % downloadedBlockGroups.length);
										} while (partId != 0 && !requiredPart);
									} else if (peerTuple instanceof QueryPartialFileTuple
											&& this.chunkHashes != null) {
										final QueryPartialFileTuple peerPartialFileTuple = (QueryPartialFileTuple) peerTuple;
										final String peerId = peerPartialFileTuple
												.getOwnerID();
										boolean requiredPart = false;
										if (!this
												.checkPartsToDownload(
														downloadedBlockGroups,
														inServiceBlockGroups,
														peerPartialFileTuple
																.getDownloadedBlockGroups())) {
											final MultipleSourcesDownloadManager instance = this;
											this.synchronizedPeerList
													.remove(peerTuple);
											this.synchronizedInServicePeerList
													.add(peerTuple);
											Thread remover = new Thread() {
												public void run() {
													_logger
															.info("Waiting... 2 minutes for:\n"
																	+ instance
																	+ "\n"
																	+ peerId);
													try {
														sleep(2 * 60 * 1000);
													} catch (InterruptedException ex) {
													}
													QueryFileTuple peer = findInServicePeer(peerId);
													if (peer != null) {
														QueryRemoteFileTuple qrft = new QueryRemoteFileTuple(
																null,
																peerPartialFileTuple
																		.getFileName(),
																peerPartialFileTuple
																		.getFileHash(),
																peerPartialFileTuple
																		.getED2KFileHash(),
																peerPartialFileTuple
																		.getSize(),
																peerPartialFileTuple
																		.getOwnerID(),
																peerPartialFileTuple
																		.getOwnerIP(),
																peerPartialFileTuple
																		.getExtendedInfos(),
																new Long(
																		System
																				.currentTimeMillis()));
														synchronizedInServicePeerList
																.remove(peer);
														synchronizedPeerList
																.add(qrft);
													}
												}
											};
											remover.start();
											continue;
										}
										partId = 0;
										do {
											if (downloadedBlockGroups[partId] == false
													&& inServiceBlockGroups[partId] == false
													&& peerPartialFileTuple
															.getDownloadedBlockGroups()[partId] == true) {
												this.synchronizedPeerList
														.remove(t);
												final long curOffset = partId
														* (blockSize * blocksPerSource);
												File resume = new File(
														WarriorAnt.chunksHome
																+ WarriorAnt.chunksPath
																+ fileHash
																+ "." + partId);
												if (resume.exists()) {
													try {
														DigestManager dm = new DigestManager();
														if (dm
																.getDigest(
																		resume)
																.equals(
																		Base16
																				.toHexString((byte[]) chunkHashes[partId]))) {
															_logger
																	.info("File part already downloaded: "
																			+ Base16
																					.toHexString((byte[]) chunkHashes[partId]));
															downloadedBlockGroups[partId] = true;
															inServiceBlockGroups[partId] = false;
															partId = (int) ((partId + this.assignedCoprime) % downloadedBlockGroups.length);
															continue;
														} else {
															resume.delete();
															_logger
																	.info("File part rejected(deleted): "
																			+ Base16
																					.toHexString((byte[]) chunkHashes[partId]));
														}
													} catch (Exception exc) {
														_logger
																.error("Error in computing digest, deleting...");
														resume.delete();
													}
												}
												_logger.info(super.toString()
														+ " Pulling file "
														+ fileName
														+ " from id " + peerId
														+ " - " + fileHash
														+ "." + partId);
												_logger.info(fileName + " "
														+ blocksPerSource + " "
														+ blockSize);
												requiredPart = true;
												this.synchronizedInServicePeerList
														.add(peerTuple);
												_logger
														.info("Idle: "
																+ this.synchronizedPeerList
																		.size()
																+ "    In service: "
																+ this.synchronizedInServicePeerList
																		.size());
												inServiceBlockGroups[partId] = true;
												PullerThread puller = new PullerThread(
														this, n, fileName,
														fileHash, curOffset,
														blocksPerSource,
														peerId, blockSize,
														WarriorAnt.chunksPath
																+ fileHash
																+ "." + partId,
														false, peerTuple,
														partId);
												puller.start();
											}
											partId = (int) ((partId + this.assignedCoprime) % downloadedBlockGroups.length);
										} while (partId != 0 && !requiredPart);
									} else if (peerTuple instanceof QueryRemoteFileTuple
											|| this.chunkHashes == null) {
										this.synchronizedPeerList
												.remove(peerTuple);
										this.synchronizedInServicePeerList
												.add(peerTuple);
										_logger.info(super.toString()
												+ " Pulling file infos: "
												+ fileName + " from id "
												+ peerTuple.getOwnerID()
												+ " - " + this.getFileHash());
										_logger
												.info("Idle: "
														+ this.synchronizedPeerList
																.size()
														+ "    In service: "
														+ this.synchronizedInServicePeerList
																.size());
										FileInfosPullerThread puller = new FileInfosPullerThread(
												this, this.n, this
														.getFileHash(),
												peerTuple.getOwnerID(),
												this.getChunkHashes() == null);
										puller.start();
									}
								}
							}
						}
					}
				}
				checkDownloadedBlockGroups();
				File store = new File(WarriorAnt.chunksHome
						+ WarriorAnt.chunksPath + fileHash + ".mul");
				ObjectOutputStream oos = new ObjectOutputStream(
						new FileOutputStream(store));
				oos.writeObject(downloadedBlockGroups);
				oos.writeObject(fileName);
				oos.writeObject(fileHash);
				oos.writeObject(ed2kFileHash);
				oos.writeObject(chunkHashes);
				int num = 0;
				for (int x = 0; x < this.downloadedBlockGroups.length; x++) {
					if (this.downloadedBlockGroups[x] == true) {
						num++;
					}
				}
				this.bytesDownloaded = num * (blocksPerSource * blockSize);
				oos.writeObject(new Long(this.bytesDownloaded));
				oos.writeObject(new Long(fileSize));
				oos.writeObject(new Integer(blockSize));
				oos.writeObject(new Long(assignedCoprime));
				oos.writeObject(extendedInfos);
				oos.close();
			}
			if (this.checkCompleted()) {
				this.removeAllPropertyChangeListeners();
				this.deactivateAutoresumeEngine();
				BackgroundEngine.getInstance().removePartialFile(getFileHash());
				this.watchDog.terminate();
				this.writingToDisk = true;
				this.n.setWritingFile();
				try {
					_logger.info("Download completed: " + this.fileHash);
					WarriorAnt.checkDownloadPath();
					File complete = new File(WarriorAnt.downloadPath
							+ (new File(this.fileName)).getName());
					FileOutputStream fos = new FileOutputStream(complete);
					for (int x = 0; x < downloadedBlockGroups.length; x++) {
						File partial = new File(WarriorAnt.chunksHome
								+ WarriorAnt.chunksPath + this.fileHash + "."
								+ x);
						FileInputStream fis = new FileInputStream(partial);
						byte[] content = new byte[blockSize];
						while (fis.available() > 0) {
							int read = fis.read(content);
							if (read > 0) {
								if (read < content.length) {
									byte[] reduced = new byte[read];
									for (int g = 0; g < read; g++) {
										reduced[g] = content[g];
									}
									content = reduced;
								}
								fos.write(content);
							}
						}
						fis.close();
						this.percWritten += partial.length();
						partial.delete();
					}
					fos.flush();
					fos.close();
					this.writingToDisk = false;
					this.n.resetWritingFile();
					BackgroundEngine.getInstance().refreshIndex();
					_logger.info("Written to disk: " + complete.getName());
					this.n.propertyChangeSupport.firePropertyChange(
							"refreshDownloadGraphic", null, this);
					File data = new File(WarriorAnt.chunksHome
							+ WarriorAnt.chunksPath + this.fileHash + ".mul");
					data.delete();
				} catch (Exception e) {
					this.writingToDisk = false;
					this.n.resetWritingFile();
					_logger.info("Couldn't write to disk: " + this.fileName);
					this.n.propertyChangeSupport.firePropertyChange(
							"refreshDownloadGraphic", null, this);
				}
			}
		} catch (Exception e) {
			_logger.error("", e);
		}
		this.interrupt = true;
	}

	public int getBlockSize() {
		return this.blockSize;
	}

	public String getFileHash() {
		return this.fileHash;
	}

	public String getED2KFileHash() {
		return this.ed2kFileHash;
	}

	public Object[] getChunkHashes() {
		return this.chunkHashes;
	}

	public String getFileName() {
		return this.fileName;
	}

	public String getExtendedInfos() {
		return this.extendedInfos;
	}

	public long getFileSize() {
		return this.fileSize;
	}

	public long getByteDownloaded() {
		return this.bytesDownloaded;
	}

	public boolean[] getDownloadedBlockGroups() {
		return this.downloadedBlockGroups;
	}

	public String getPercentage() {
		return getIntPercentage() + "%";
	}

	public int getIntPercentage() {
		long den = this.downloadedBlockGroups.length * blocksPerSource;
		if (den == 0)
			return 0;
		int num = 0;
		for (int x = 0; x < this.downloadedBlockGroups.length; x++)
			if (this.downloadedBlockGroups[x] == true)
				num++;
		num = (num * blocksPerSource) + chunckBlocksCounter;
		double perc = (num * 1.0 / den) * 100;
		if (perc < 0)
			perc = 0;
		if (perc > 100)
			perc = 100;
		return (int) perc;
	}

	public String speedToString() {
		double speed = this.getSpeedValue();
		return ("" + speed).substring(0, ("" + speed).indexOf(".") + 2)
				+ "KB/s ";
	}

	public double getSpeedValue() {
		if (this.checkCompleted() || this.inServicePeerList.size() == 0)
			return 0;
		double elapsed = (System.currentTimeMillis() - this.startTime) / 1000.0;
		long downloadedGroups = 0;
		for (int x = 0; x < this.downloadedBlockGroups.length; x++) {
			if (this.downloadedBlockGroups[x] == true) {
				downloadedGroups++;
			}
		}
		downloadedGroups = (downloadedGroups * blocksPerSource)
				+ chunckBlocksCounter;
		double speed;
		if (elapsed > 0) {
			speed = (((downloadedGroups * blockSize) - this.initialBytesDownloaded) / elapsed)
					/ ConstantUtil.KBYTES;
		} else
			speed = 0;
		return speed > 0 ? speed : 0;
	}

	public boolean isDownloaded() {
		return this.checkCompleted() && !(this.writingToDisk);
	}

	public String toString() {
		String result;
		if (this.interrupt == true && !this.checkCompleted()) {
			result = ji.JI.i("Interrupting");
		} else if (this.chunkHashes == null) {
			result = ji.JI.i("Getting hash map");
		} else if (!this.checkCompleted()) {
			result = this.getPercentage();
		} else if (this.isDownloaded()) {
			result = ji.JI.i("Completed");
		} else {
			result = ji.JI.i("Writing...");
		}
		while (result.length() < 5)
			result += " ";
		result += "  " + this.speedToString();
		while (result.length() < 22)
			result += " ";
		if (!this.checkCompleted()) {
			result += " " + StringUtil.fileSize2Str(this.bytesDownloaded) + "/"
					+ StringUtil.fileSize2Str(this.getFileSize()) + " ";
		} else if (this.writingToDisk) {
			result += " " + StringUtil.fileSize2Str(this.percWritten) + "/"
					+ StringUtil.fileSize2Str(this.getFileSize()) + " ";
		} else {
			result += StringUtil.fileSize2Str(this.getFileSize()) + " ";
		}
		while (result.length() < 52)
			result += " ";
		result += " " + this.getFileHash() + "     ";
		result += this.getFileName();
		return result;
	}

	public boolean checkPartsToDownload(boolean[] downloadedBlockGroups,
			boolean[] inServiceBlockGroups, boolean[] remoteParts)
			throws Exception {
		if (downloadedBlockGroups.length != remoteParts.length)
			throw new Exception("Array sizes do not match");
		for (int x = 0; x < downloadedBlockGroups.length; x++) {
			if (downloadedBlockGroups[x] == false
					&& inServiceBlockGroups[x] == false
					&& remoteParts[x] == true)
				return true;
		}
		return false;
	}

	public void checkDownloadedBlockGroups() {
		for (int x = 0; x < downloadedBlockGroups.length; x++) {
			File currentFile = new File(WarriorAnt.chunksHome
					+ WarriorAnt.chunksPath + this.getFileHash() + "." + x);
			if (x != downloadedBlockGroups.length - 1) {
				if (currentFile.exists()
						&& currentFile.length() == (blocksPerSource * blockSize)) {
					downloadedBlockGroups[x] = true;
				} else {
					downloadedBlockGroups[x] = false;
				}
			} else {
				if (currentFile.exists()
						&& (currentFile.length() == this.fileSize
								- ((downloadedBlockGroups.length - 1) * (blocksPerSource * blockSize)))) {
					downloadedBlockGroups[x] = true;
				} else {
					downloadedBlockGroups[x] = false;
				}
			}
		}
	}

	public QueryFileTuple findPeer(String peerId) {
		for (int x = 0; x < this.peerList.size(); x++) {
			if (((QueryFileTuple) this.peerList.get(x)).getOwnerID().equals(
					peerId))
				return (QueryFileTuple) this.peerList.get(x);
		}
		return null;
	}

	public QueryFileTuple findInServicePeer(String peerId) {
		for (int x = 0; x < this.inServicePeerList.size(); x++) {
			if (((QueryFileTuple) this.inServicePeerList.get(x)).getOwnerID()
					.equals(peerId))
				return (QueryFileTuple) this.inServicePeerList.get(x);
		}
		return null;
	}

	public void addAutoresumeEngine(AutoresumeEngine ae) {
		if (this.ae != null && !this.ae.deactivated())
			this.ae.deactivate();
		this.ae = ae;
		if (this.ae != null && !this.ae.isAlive()) {
			this.ae.start();
			while (!this.ae.isAlive()) {
				try {
					Thread.currentThread().sleep(1000); // .yield()
				} catch (InterruptedException ex) {
				}
			}
		}
	}

	public AutoresumeEngine getAutoresumeEngine() {
		return this.ae;
	}

	public void deactivateAutoresumeEngine() {
		if (this.ae != null && !this.ae.deactivated()) {
			this.ae.deactivate();
			this.ae = null;
		}
	}

	public boolean isAutoresumeActive() {
		if (this.ae != null && !this.ae.deactivated())
			return true;
		else
			return false;
	}

	public boolean checkCompleted() {
		boolean completed = true;
		for (int x = 0; x < downloadedBlockGroups.length; x++) {
			if (downloadedBlockGroups[x] == false) {
				completed = false;
			}
		}
		return completed;
	}
}

class DownloadWatchDog extends Thread {
	public static long downloadTimeout = Ant.messageTimeout
			* Ant.maxRetransmissions;

	long lastPartReceivedTime;

	MultipleSourcesDownloadManager msdm = null;

	boolean interrupt = false;

	long byteDownloadedTracer;

	long curByteDownloadedTracer;

	long startTimeTracer;

	long curStartTimeTracer;

	static Logger _logger = Logger.getLogger(DownloadWatchDog.class.getName());

	public DownloadWatchDog(MultipleSourcesDownloadManager msdm) {
		super();
		this.msdm = msdm;
		lastPartReceivedTime = System.currentTimeMillis();
		this.setPriority(6);

		this.byteDownloadedTracer = msdm.initialBytesDownloaded;
		this.startTimeTracer = msdm.startTime;
		this.curByteDownloadedTracer = msdm.initialBytesDownloaded;
		this.curStartTimeTracer = msdm.startTime;
	}

	public void setReceivedPart() {
		lastPartReceivedTime = System.currentTimeMillis();
	}

	public void terminate() {
		this.interrupt = true;
	}

	public void run() {
		int resetActivator = 0;
		while (msdm.isAlive() && !interrupt) {
			try {
				sleep(5000);
			} catch (Exception e) {
				_logger.error("", e);
			}
			this.msdm.resume();
			long elapsed = System.currentTimeMillis()
					- this.lastPartReceivedTime;
			if (elapsed > downloadTimeout) {
				msdm.propertyChangeSupport.firePropertyChange(
						"multipleDownloadTimedOut", null, msdm);
				_logger.info(this.msdm + "\ntimed out!");
				return;
			} else {
				this.byteDownloadedTracer = this.curByteDownloadedTracer;
				this.startTimeTracer = this.curStartTimeTracer;

				long downloadedGroups = 0;
				for (int x = 0; x < this.msdm.downloadedBlockGroups.length; x++) {
					if (this.msdm.downloadedBlockGroups[x] == true) {
						downloadedGroups++;
					}
				}
				downloadedGroups = (downloadedGroups * msdm.blocksPerSource)
						+ msdm.chunckBlocksCounter;
				this.curByteDownloadedTracer = downloadedGroups
						* msdm.blockSize;
				this.curStartTimeTracer = System.currentTimeMillis();

				msdm.initialBytesDownloaded = this.byteDownloadedTracer;
				msdm.startTime = this.startTimeTracer;
				msdm.propertyChangeSupport.firePropertyChange(
						"refreshDownloadGraphic", null, msdm);

				// _logger.info("WD "+this.msdm+"\nis running!");
				this.msdm.n.prunePartialFiles();
			}
		}
		_logger.info(this.msdm + "\ninterrupted!");
	}
}

class PullerThread extends Thread implements PropertyChangeListener {
	long lastPartReceivedTime;

	boolean terminate = false;

	MultipleSourcesDownloadManager caller;

	WarriorAnt n;

	String fileName;

	String fileHash;

	long offset;

	long blocks;

	String peerId;

	int blockSize;

	String localFileName;

	boolean resume;

	QueryFileTuple peerTuple;

	int partId;

	static Logger _logger = Logger.getLogger(PullerThread.class.getName());

	PullerThread(MultipleSourcesDownloadManager caller, WarriorAnt n,
			String fileName, String fileHash, long offset, long blocks,
			String peerId, int blockSize, String localFileName, boolean resume,
			QueryFileTuple peerTuple, int partId) {
		this.n = n;
		this.caller = caller;
		this.fileName = fileName;
		this.fileHash = fileHash;
		this.offset = offset;
		this.blocks = blocks;
		this.peerId = peerId;
		this.blockSize = blockSize;
		this.localFileName = localFileName;
		this.resume = resume;
		this.peerTuple = peerTuple;
		this.partId = partId;
		this.caller.propertyChangeSupport.addPropertyChangeListener(this);
		this.setPriority(6);
	}

	public void run() {
		try {
			n.pullFile(this.fileName, this.fileHash, this.offset, this.blocks,
					this.peerId, this.blockSize, this.localFileName,
					this.resume);
			lastPartReceivedTime = System.currentTimeMillis();
			while (!terminate) {
				sleep(5000);
				long elapsed = System.currentTimeMillis()
						- this.lastPartReceivedTime;
				if (!this.caller.checkCompleted()
						&& (this.caller.interrupt || elapsed > DownloadWatchDog.downloadTimeout)) {
					throw new Exception("Pull failed: "
							+ this.fileHash.substring(0, 10) + " "
							+ this.peerId.substring(0, 10));
				}
			}
		} catch (Exception e) {
			_logger.error("", e);
			synchronized (caller.inServiceBlockGroups) {
				synchronized (caller.synchronizedInServicePeerList) {
					n.propertyChangeSupport.firePropertyChange(
							"filePullerThreadRequestError", null, this);
				}
			}
		}
		this.caller.propertyChangeSupport.removePropertyChangeListener(this);
	}

	public void propertyChange(PropertyChangeEvent e) {
		if (e.getPropertyName().equals("pullerThreadUpdatePart")
				&& e.getNewValue().equals(this.peerId)) {
			this.lastPartReceivedTime = System.currentTimeMillis();
		} else if (e.getPropertyName().equals("pullerThreadCompletedPart")
				&& e.getNewValue().equals(this.peerId)) {
			this.terminate = true;
			_logger.info("Puller thread completed: " + this.fileHash + " "
					+ this.partId);
		} else if (e.getPropertyName().equals("pullerThreadPartError")
				&& e.getNewValue().equals(this.peerId)) {
			this.terminate = true;
			_logger.info("Puller thread terminated: " + this.fileHash + " "
					+ this.partId);
		}
	}
}

class FileInfosPullerThread extends Thread implements PropertyChangeListener {
	long initTime;

	MultipleSourcesDownloadManager caller;

	WarriorAnt n;

	String fileHash;

	String peerId;

	boolean getChunkHashes;

	boolean terminate = false;

	static Logger _logger = Logger.getLogger(FileInfosPullerThread.class
			.getName());

	FileInfosPullerThread(MultipleSourcesDownloadManager caller, WarriorAnt n,
			String fileHash, String peerId, boolean getChunkHashes) {
		this.n = n;
		this.caller = caller;
		this.fileHash = fileHash;
		this.peerId = peerId;
		this.getChunkHashes = getChunkHashes;
		this.caller.propertyChangeSupport.addPropertyChangeListener(this);
	}

	public void run() {
		initTime = System.currentTimeMillis();
		try {
			n.pullFileInfos(this.fileHash, this.peerId, this.getChunkHashes);
			initTime = System.currentTimeMillis();
			while (!terminate) {
				sleep(5000);
				long elapsed = System.currentTimeMillis() - this.initTime;
				if (this.caller.interrupt
						|| elapsed > Ant.messageTimeout
								* Ant.maxRetransmissions) {
					throw new Exception("Infos pull failed: "
							+ this.fileHash.substring(0, 10) + " "
							+ this.peerId.substring(0, 10));
				}
			}
		} catch (Exception e) {
			_logger.error("", e);
			synchronized (caller.inServiceBlockGroups) {
				synchronized (caller.synchronizedInServicePeerList) {
					n.propertyChangeSupport.firePropertyChange(
							"infosPullerThreadRequestError", null, this);
				}
			}
		}
		this.caller.propertyChangeSupport.removePropertyChangeListener(this);
	}

	public void propertyChange(PropertyChangeEvent e) {
		if (e.getPropertyName().equals("fileInfosPullCompleteThread")
				&& e.getNewValue().equals(this.peerId)) {
			this.terminate = true;
			_logger.info("Puller thread completed: " + this.fileHash);
		} else if (e.getPropertyName().equals("fileInfosPullErrorThread")
				&& e.getNewValue().equals(this.peerId)) {
			this.terminate = true;
			_logger.info("Infos puller thread terminated: " + this.fileHash);
		}
	}
}
